/*                                                                                                                           
 * JFFS3 -- Journalling Flash File System, Version 3.                                                                        
 *                                                                                                                           
 * Copyright (C) 2006  Ferenc Havasi <havasi@inf.u-szeged.hu>,                                                               
 *                     Zoltan Sogor <weth@inf.u-szeged.hu>,                                                                  
 *                     Matyas Majzik <admin@newarcana.com>,                                                               
 *                     University of Szeged, Hungary                                                                         
 *
 * This software is distributed under the GNU GPL v2.
 * For licensing information, see the file 'LICENCE' in this directory.                                                             
 *                                                                                                                           
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "nand.h"
#include "sleep.h"

extern struct flash_descriptor flash_d;

static struct flash_memory flash = {
    .status = FLASH_CLOSED,
};


/*
    allocate_structures()
    Helper function to setup_flash(). It allocates the used
    data structures.
*/
void allocate_structures() 
    {
    int i,j;

    flash.ebs = (struct eraseblock_descriptor *)malloc(sizeof(struct
                                                              eraseblock_descriptor) * flash.erase_blocks);
    for (i = 0; i < flash.erase_blocks; i++)
        {
        flash.ebs[i].status = FLASH_BLOCK_READY;
        flash.ebs[i].erase_count = 0;
        flash.ebs[i].pages = (struct page_descriptor *)
                             malloc(sizeof(struct page_descriptor) * flash.erase_block_pages);
        for (j = 0; j < flash.erase_block_pages; j++)
            {
            flash.ebs[i].pages[j].status=FLASH_PAGE_UNALLOCATED;
            }
        }
    }

/*
    flash_setup()
    Setup a flash memory according to 'fl_d'. Check if it is a valid flash.
*/
int flash_setup(struct flash_descriptor *fl_d) 
    {
    //validating parameters
    if (!((fl_d->size % 1024 == 0) && (fl_d->size % fl_d->erase_size == 0)&&
          (fl_d->erase_size % fl_d->page_size == 0 )))
        {
        return(FLASH_INVALID);
        }
    else
        {
        fl_d->read = flash_read;
        fl_d->write = flash_write;
        fl_d->writev = flash_writev;
        fl_d->erase = flash_erase_block;
        fl_d->block_markbad = flash_mark_bad_block;
        fl_d->block_isbad = flash_is_bad_block;
        }

    if (flash.status == FLASH_CLOSED)
        {
        flash.erase_blocks = fl_d->size / fl_d->erase_size;
        flash.erase_block_pages = fl_d->erase_size / fl_d->page_size;
        allocate_structures();
        flash.status = FLASH_SETUP; //flash is ready to use
        if ((fl_d->flags & DO_DELAYS) == DO_DELAYS)
            {
            p_calibrate(); //calibrating precision timer
            }
        }
    else return(-2);

    return(0);
    }

/*
    flash_close()
    Closes the flash, frees all used memory.
*/
int flash_close() 
    {
    int i,j;

    if (flash.status != FLASH_SETUP)
        {
        return(-1);
        }
    for (i = 0; i < flash.erase_blocks; i++)
        {
        for (j = 0; j < flash.erase_block_pages; j++)
            {
            if (flash.ebs[i].pages[j].status != FLASH_PAGE_UNALLOCATED)
                {
                free(flash.ebs[i].pages[j].data);
                }
            }
        free(flash.ebs[i].pages);
        }
    free(flash.ebs);
    flash.status = FLASH_CLOSED;
    return(0);
    }

/*
    flash_get_descriptor()
*/
struct flash_descriptor *flash_get_descriptor()
    {
    if (flash.status != FLASH_SETUP)
        return(NULL);
    return(&flash_d);
    }

/*
    move_forward()
    Moving along the flash memory forward by increasing page number and erase
    block number correctly
*/
int move_forward(uint32_t *erase_block, uint32_t *page) 
    {
    if ((*page) + 1 < flash.erase_block_pages)
        {
        (*page)++;
        }
    else
        {
        if ((*erase_block) + 1 < flash.erase_blocks)
            {
            (*page) = 0;
            (*erase_block)++;
            }
        else return(-1);
        }

    return(0);
    }

/*
    read_page()
    reading one page from the flash memory
*/
int read_page(uint8_t *buf, uint32_t erase_block, uint32_t page, int offset, int len) 
    {
//	struct timespec ts;
    if ((flash_d.flags & DO_DELAYS) == DO_DELAYS)
        {
/*		ts.tv_sec = 0;
        ts.tv_nsec = ACCESS_DELAY;
        nanosleep(&ts, NULL);*/
        p_sleep(ACCESS_DELAY);
        }
    if ((flash.ebs[erase_block].pages[page].status & FLASH_PAGE_WRITTEN) == FLASH_PAGE_WRITTEN)
        {
        memcpy(buf, flash.ebs[erase_block].pages[page].data + offset, len);
        }
    else
        {
        //for (i = 0; i < len; i++) buf[i] = FLASH_ERASE_BYTE;
        memset(buf, FLASH_ERASE_BYTE, len);
        }

    return(len);
    }

/*
    flash_read()
    Reads from one or more blocks according to 'ofs' and 'len'. This function
    reads from all blocks belongs to the range of ofs+len - ofs in the flash
    memory.
*/
int flash_read(uint64_t ofs, uint64_t len, uint64_t *retlen, uint8_t *buf) 
    {
    uint32_t start_offset, stop_offset;
    uint32_t start_block, stop_block, start_page, stop_page;
    uint32_t i, buf_ofs;
    uint32_t datasize, pagestoread;
    uint32_t temp;

    if (flash.status != FLASH_SETUP)
        return(-1);
    if (ofs >= flash_d.size)
        return(FLASH_INVALID_OFFSET);
    if ((ofs + len) > flash_d.size)
        return(FLASH_OUT_OF_RANGE);

    /*
    calculating number of first and last erase blocks and 
    first page in the first eraseblock and offset in that page
    and last page in the last eraseblock and the offset
    */
    start_block = ofs / flash_d.erase_size;
    start_page = (ofs % flash_d.erase_size) / flash_d.page_size;
    start_offset = (ofs % flash_d.erase_size) % flash_d.page_size;

    stop_block = (ofs + len) / flash_d.erase_size;
    stop_page = ((ofs + len) % flash_d.erase_size) / flash_d.page_size;
    stop_offset = ((ofs + len) % flash_d.erase_size) % flash_d.page_size;

    //calculate how many whole pages to read
    datasize = len - stop_offset - (flash_d.page_size - start_offset);
    pagestoread = datasize / flash_d.page_size;

    if (datasize % flash_d.page_size != 0)
        return(-10); //internal error

    *retlen = 0;

    if ((flash.ebs[start_block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
        return(FLASH_BAD_BLOCK);

    // reading first page
    if ((start_offset + len) > flash_d.page_size)
        {
        if (((*retlen) = read_page(buf, start_block, start_page, start_offset,
                                   flash_d.page_size - start_offset)) == 0)
            return(FLASH_READ_ERROR);
        buf_ofs = flash_d.page_size - start_offset;
        }
    else
        {
        if (read_page(buf, start_block, start_page, start_offset, len ) == 0)
            return(FLASH_READ_ERROR);
        *retlen = len;

        return(0); //just finished
        }


    // reading all pages between start and stop
    for (i = 0; i < pagestoread; i++)
        {
        // moving forward
        if (move_forward(&start_block, &start_page) != 0)
            {
            return(FLASH_OUT_OF_RANGE); //not enough space
            }
        if ((flash.ebs[start_block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
            return(FLASH_BAD_BLOCK);
        if ((temp = read_page(buf + buf_ofs, start_block, start_page, 0, flash_d.page_size)) == 0)
            return(FLASH_READ_ERROR);

        (*retlen) += temp;
        buf_ofs += flash_d.page_size;
        }

    // reading last page
    // moving forward
    if (move_forward(&start_block, &start_page) != 0)
        {
        if (stop_offset == 0)
            {
            return(0);
            }
        return(FLASH_OUT_OF_RANGE); //not enough space
        }
    if ((flash.ebs[start_block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
        {
        return(FLASH_BAD_BLOCK);
        }
    if (stop_offset != 0)
        {
        if ((temp = read_page(buf+buf_ofs, start_block, start_page, 0, stop_offset)) == 0)
            return(FLASH_READ_ERROR);

        (*retlen) += temp;
        buf_ofs += stop_offset;
        }

    return(0);
    }

/*
    write_page();
    writing one page to flash memory. Returns 0 if failed or len if succeed.
*/
int write_page(const uint8_t *buf, uint32_t erase_block, uint32_t page, int offset, int len) 
    {
//	struct timespec ts;
    if ((flash_d.flags & DO_DELAYS) == DO_DELAYS)
        {
/*		ts.tv_sec = 0;
        ts.tv_nsec = PROGRAMM_DELAY;
        //nanosleep(&ts, NULL);
        udelay(PROGRAMM_DELAY);*/
        p_sleep(PROGRAMM_DELAY);
        }
    if ((flash.ebs[erase_block].pages[page].status &
         FLASH_PAGE_WRITTEN) == FLASH_PAGE_WRITTEN)
        {
        return(0);
        }
    else
        {
        if ((offset + len) > flash_d.page_size)
            return(0); //internal error
        if ((flash.ebs[erase_block].pages[page].status &
             FLASH_PAGE_ALLOCATED) == FLASH_PAGE_ALLOCATED)
            {
            flash.ebs[erase_block].pages[page].status = FLASH_PAGE_WRITTEN | FLASH_PAGE_ALLOCATED;
            memcpy(flash.ebs[erase_block].pages[page].data + offset, buf, len);
            }
        else
            {
            flash.ebs[erase_block].pages[page].data = malloc(sizeof(uint8_t) * flash_d.page_size);
            flash.ebs[erase_block].pages[page].status = FLASH_PAGE_WRITTEN | FLASH_PAGE_ALLOCATED;
            memcpy(flash.ebs[erase_block].pages[page].data + offset, buf, len);
            }
        }

    return(len);
    }

/*
    flash_write()
    Writes into one or more blocks according to 'ofs' and 'len'. This function
    writes to all blocks belongs to the range of ofs+len - ofs in the flash
    memory.
*/
int flash_write(uint64_t ofs, uint64_t len, uint64_t *retlen, const uint8_t *buf) 
    {
    uint32_t start_offset, stop_offset;
    uint32_t start_block, stop_block, start_page, stop_page;
    uint32_t i, buf_ofs;
    uint32_t datasize, pagestowrite;
    uint32_t temp;

    if (flash.status != FLASH_SETUP)
        return(-1);
    if (ofs >= flash_d.size)
        return(FLASH_INVALID_OFFSET);
    if ((ofs+len) > flash_d.size)
        return(FLASH_OUT_OF_RANGE);

    /*
    calculating number of first and last erase blocks and 
    first page in the first eraseblock and offset in that page
    and last page in the last eraseblock and the offset
    */
    start_block = ofs/flash_d.erase_size;
    start_page = (ofs%flash_d.erase_size)/flash_d.page_size;
    start_offset = (ofs%flash_d.erase_size)%flash_d.page_size;

    stop_block = (ofs + len) / flash_d.erase_size;
    stop_page = ((ofs + len) % flash_d.erase_size) / flash_d.page_size;
    stop_offset = ((ofs + len) % flash_d.erase_size) % flash_d.page_size;

    //calculate how many whole pages to write
    datasize = len - stop_offset - (flash_d.page_size - start_offset);
    pagestowrite = datasize / flash_d.page_size;
    if (datasize % flash_d.page_size != 0)
        return(-10); //internal error

    (*retlen) = 0;
    if ((flash.ebs[start_block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
        return(FLASH_BAD_BLOCK);

    // writing first page
    if ((start_offset + len) > flash_d.page_size)
        {
        if (((*retlen) = write_page(buf, start_block, start_page, start_offset,
                                    flash_d.page_size - start_offset)) == 0)
            return(FLASH_PAGE_ALREADY_WRITTEN);
        buf_ofs = flash_d.page_size - start_offset;
        }
    else
        {
        if (write_page(buf, start_block, start_page, start_offset, len) == 0)
            return(FLASH_PAGE_ALREADY_WRITTEN);
        (*retlen) = len;

        return(0); //just finished
        }


    // writing all pages between start and stop
    for (i = 0; i<pagestowrite; i++)
        {
        // moving forward
        if (move_forward(&start_block, &start_page) != 0)
            return(FLASH_OUT_OF_RANGE); //not enough space
        if ((flash.ebs[start_block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
            return(FLASH_BAD_BLOCK);
        temp = write_page(buf + buf_ofs, start_block, start_page, 0,
                          flash_d.page_size);
        if (temp == 0)
            {
            return(FLASH_PAGE_ALREADY_WRITTEN);
            }
        (*retlen) += temp;
        buf_ofs += flash_d.page_size;
        }

    // writing last page
    if (move_forward(&start_block,&start_page) != 0)
        {
        if (stop_offset == 0)
            {
            return(0);
            }
        return(FLASH_OUT_OF_RANGE); //not enough space
        }
    if ((flash.ebs[start_block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
        return(FLASH_BAD_BLOCK);
    if (stop_offset != 0)
        {
        temp = write_page(buf + buf_ofs, start_block, start_page, 0,
                          stop_offset);
        if (temp == 0)
            return(FLASH_PAGE_ALREADY_WRITTEN);
        (*retlen) += temp;
        buf_ofs += stop_offset;
        }

    return(0);
    }

/*
    flash_writeev()
    using kvec struct and passing to the normal flash_write
*/
int flash_writev(const struct kvec *vecs, uint64_t count, uint64_t to, uint64_t *retlen) 
    {
    int i;
    int ret;
    uint64_t len = 0;
    uint8_t *buf, *walk;

    for (i = 0; i < count; i++)
        {
        len += vecs[i].iov_len;
        }

    buf = malloc(len);
    if (!buf)
        {
        return(FLASH_INVALID);
        }

    memset(buf, FLASH_ERASE_BYTE, len);
    walk = buf;

    for (i = 0; i < count; i++)
        {
        memcpy(walk, vecs[i].iov_base, vecs[i].iov_len);
        walk += vecs[i].iov_len;
        }

    ret = flash_write(to, len, retlen, buf);
    free(buf);

    return(ret);
    }

/*
    flash_erase_block()
    Erases one or more block according to 'ofs' and 'len'. This function
    erases all blocks belongs to the range of ofs - ofs+len-1 in the flash memory.
*/
int flash_erase_block(uint64_t ofs, uint64_t len) 
    {
//	struct timespec ts;
    uint32_t start, stop, i, j;

    if (flash.status != FLASH_SETUP)
        return(-1);
    if (ofs >= flash_d.size)
        return(FLASH_INVALID_OFFSET);
    if ((ofs + len) > flash_d.size)
        return(FLASH_OUT_OF_RANGE);

    //calculating number of first and last erase block
    start = ofs / flash_d.erase_size;
    stop = (ofs + len - 1) / flash_d.erase_size;

    for (i = start; i <= stop; i++)
        {
        flash.ebs[i].status = FLASH_BLOCK_READY;
        flash.ebs[i].erase_count++;

        for (j = 0; j < flash.erase_block_pages; j++)
            {
            flash.ebs[i].pages[j].status &= ~FLASH_PAGE_WRITTEN;
            if ((flash_d.flags & DO_DELAYS) == DO_DELAYS)
                {
/*				ts.tv_sec = 0;
                ts.tv_nsec = ERASE_DELAY;
                nanosleep(&ts, NULL);*/
                p_sleep(ERASE_DELAY);
                }
            if ((flash.ebs[i].pages[j].status & FLASH_PAGE_ALLOCATED)
                == FLASH_PAGE_ALLOCATED)
                {
                //for(k = 0; k < flash_d.page_size; k++) flash.ebs[i].pages[j].data[k] = FLASH_ERASE_BYTE;
                memset(flash.ebs[i].pages[j].data, FLASH_ERASE_BYTE, flash_d.page_size);
                }
            }
        }

    return(0);
    }

/*
    flash_mark_bad_block()
    Marks a block bad even if it is already bad.
*/
int flash_mark_bad_block(uint64_t ofs) 
    {
    uint32_t block;

    if (flash.status != FLASH_SETUP)
        return(-1);
    if (ofs >= flash_d.size)
        return(FLASH_INVALID_OFFSET);

    block = ofs / flash_d.erase_size;
    flash.ebs[block].status = FLASH_BLOCK_BAD;

    return(0);
    }

/*
    flash_is_bad_block()
    Checks if the specified address belongs to a bad block.
    Return 1 if that block is bad or 0 if that block is good.
*/
int flash_is_bad_block(uint64_t ofs) 
    {
    uint32_t block;

    if (flash.status != FLASH_SETUP)
        return(-1);
    if (ofs >= flash_d.size)
        return(FLASH_INVALID_OFFSET);

    block = ofs / flash_d.erase_size;
    if ((flash.ebs[block].status & FLASH_BLOCK_BAD) == FLASH_BLOCK_BAD)
        return(1);
    else return(0);
    }

/*
    flash_page_is_written()
    Return 0 if the specified flash page is alread written
    otherwise returns -1
*/
int flash_page_is_written(uint64_t ofs) 
    {
    uint32_t block,page;

    if (flash.status != FLASH_SETUP)
        return(-1);
    if (ofs >= flash_d.size)
        return(FLASH_INVALID_OFFSET);

    block = ofs / flash_d.erase_size;
    page = (ofs % flash_d.erase_size) / flash_d.page_size;
    if ((flash.ebs[block].pages[page].status & FLASH_PAGE_WRITTEN) == 0)
        return(0);
    else return(-1);
    }

/*
    flash_to_disk()
    Writing entire flash memory to disk by using two separare files.
    One for the data structures need to rebuild the flash from disk
    and one for the actual data.
    Return 0 on success, -1 if structure file cannot be opened, -2
    if image file cannot be opened
*/
int flash_to_disk(const char *file_name) 
    {
    FILE *fh;
    char filename[256];
    uint32_t i,j;
    uint8_t empty[flash_d.page_size];

    //opening file for the actual data
    strcpy(filename, file_name);
    strcat(filename, ".img");
    fh = fopen(filename, "wb");
    if (!fh)
        return(-2);

    //for( i=0; i<flash_d.page_size; i++ ) empty[i]=FLASH_ERASE_BYTE;
    memset(empty, FLASH_ERASE_BYTE, flash_d.page_size);

    for (i = 0; i < flash.erase_blocks; i++)
        {
        for (j = 0; j < flash.erase_block_pages; j++)
            {
            if (((flash.ebs[i].pages[j].status) &
                 FLASH_PAGE_ALLOCATED) == FLASH_PAGE_ALLOCATED)
                {
                fwrite(flash.ebs[i].pages[j].data, sizeof(uint8_t),
                       flash_d.page_size,fh);
                }
            else
                {
                fwrite(empty, sizeof(uint8_t), flash_d.page_size, fh);
                }
            }
        }
    fclose(fh);

    //opening file for the data structures
    strcpy(filename, file_name);
    strcat(filename, ".inf");
    fh = fopen(filename, "wb");
    if (!fh)
        return(-1);

    // writing header
    fprintf(fh, "%s", "NANDv1");
    fwrite(&flash_d.size, sizeof(uint32_t), 1, fh);
    fwrite(&flash_d.erase_size, sizeof(uint32_t), 1, fh);
    fwrite(&flash_d.page_size, sizeof(uint32_t), 1, fh);
    fwrite(&flash_d.flags, sizeof(uint32_t), 1, fh);

    // writing all eraseblock info and pages info
    for (i = 0; i < flash.erase_blocks; i++)
        {
        fwrite(&flash.ebs[i].status, sizeof(uint8_t), 1, fh);
        fwrite(&flash.ebs[i].erase_count, sizeof(uint32_t), 1, fh);
        for (j = 0; j<flash.erase_block_pages; j++)
            {
            fwrite(&flash.ebs[i].pages[j].status, sizeof(uint8_t), 1, fh);
            }
        }
    fclose(fh);

    return(0);
    }
/*
    flash_from_disk()
    Reading entire flash memory from disk by using two separare files.
    One for the data structures need to rebuild the flash from disk
    and one for the actual data. 
    Return 0 on success, -1 if structure file cannot be opened, -2
    if image file cannot be opened, -3 if the file not in the proper format.
*/
int flash_from_disk(const char *file_name) 
    {
    int i,j;
    FILE *fh;
    FILE *fhdata;
    char filename[256];

    //opening files
    strcpy(filename, file_name);
    strcat(filename, ".inf");
    fh = fopen(filename, "rb");
    strcpy(filename, file_name);
    strcat(filename, ".img");
    fhdata = fopen(filename,"rb");
    if (!fh)
        return(-1);
    if (!fhdata)
        return(-2);

    //reading header
    fseek(fh, 6, SEEK_SET);
    fread(&flash_d.size, sizeof(uint32_t), 1, fh);
    fread(&flash_d.erase_size, sizeof(uint32_t), 1, fh);
    fread(&flash_d.page_size, sizeof(uint32_t), 1, fh);
    fread(&flash_d.flags, sizeof(uint32_t), 1, fh);

    //if something goes wrong remain the flash in closed state
    flash.status = FLASH_CLOSED;

    //reading both the datastructures and the data from the files
    if ((flash_d.size % 1024 == 0) &&
        (flash_d.size % flash_d.erase_size == 0) &&
        (flash_d.erase_size % flash_d.page_size == 0 ))
        {
        flash.erase_blocks = flash_d.size / flash_d.erase_size;
        flash.erase_block_pages = flash_d.erase_size / flash_d.page_size;

        flash.ebs = (struct eraseblock_descriptor *)malloc(sizeof(struct
                                                                  eraseblock_descriptor) * flash.erase_blocks);
        for (i = 0; i < flash.erase_blocks; i++)
            {
            fread(&flash.ebs[i].status, sizeof(uint8_t), 1, fh);
            fread(&flash.ebs[i].erase_count, sizeof(uint32_t), 1, fh);
            flash.ebs[i].pages = (struct page_descriptor *)
                                 malloc(sizeof(struct page_descriptor) * flash.erase_block_pages);
            for (j = 0; j < flash.erase_block_pages; j++)
                {
                fread(&flash.ebs[i].pages[j].status, sizeof(uint8_t), 1, fh);
                flash.ebs[i].pages[j].status |= FLASH_PAGE_ALLOCATED;

                //reading the actual data
                flash.ebs[i].pages[j].data = malloc(sizeof(uint8_t) *
                                                    flash_d.page_size);
                fread(flash.ebs[i].pages[j].data, sizeof(uint8_t),
                      flash_d.page_size,fhdata);
                }
            }
        flash.status = FLASH_SETUP; //flash is ready to use
        fclose(fh);
        fclose(fhdata);
        }
    else
        {
        fclose(fh);
        fclose(fhdata);
        return(-3);  //incorrect file
        }
    return(0);
    }
